package com.slightech.marvin.api.doc.autoconfigure;

import com.slightech.marvin.api.doc.annotation.MarvinApi;
import com.slightech.marvin.api.doc.annotation.MarvinApiDoc;
import com.slightech.marvin.api.doc.model.*;
import com.slightech.marvin.api.doc.parser.FieldParser;
import com.slightech.marvin.api.doc.parser.IFieldParser;
import lombok.val;
import lombok.var;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author willardwang
 * @description
 * @date 2019/07/29
 */
public class ApiDocScanner implements BeanPostProcessor {
    private static final Logger logger = LoggerFactory.getLogger(ApiDocScanner.class);
    private static final IFieldParser fieldParser = new FieldParser();
    private Set<ApiGroup> apiSet = new TreeSet<>();
    private Map<Integer, Api> allApi = new HashMap<>();
    private AtomicInteger apiIdCounter = new AtomicInteger();
    private Map<String, ApiCount> apiCounterMap = new HashMap<>();
    private boolean enabled;

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (!this.enabled) {
            return bean;
        }
        MarvinApiDoc marvinApiDoc = bean.getClass().getAnnotation(MarvinApiDoc.class);
        RestController restController = bean.getClass().getAnnotation(RestController.class);
        if (restController == null || marvinApiDoc == null) {
            return bean;
        }
        logger.info("扫描到接口:{}", marvinApiDoc.value());
        RequestMapping namespaceRequestMapping = bean.getClass().getAnnotation(RequestMapping.class);
        var namespaceUri = "";
        if (namespaceRequestMapping != null) {
            namespaceUri = namespaceRequestMapping.value()[0];
        }
        val apiGroup = new ApiGroup();
        apiGroup.setNameSpaceUri(namespaceUri);
        apiGroup.setName(marvinApiDoc.value());
        apiGroup.setOrder(marvinApiDoc.order());
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
        Map<Integer, Api> apiMap = new HashMap<>();
        for (Method method : methods) {
            val requestMapping = method.getAnnotation(RequestMapping.class);
            if (requestMapping == null) {
                continue;
            }
            val requestUri = apiGroup.getNameSpaceUri() + "/" + requestMapping.value()[0];
            val marvinApi = method.getAnnotation(MarvinApi.class);
            this.countApi(marvinApi);
            val api = new Api();
            int apiId = apiIdCounter.incrementAndGet();
            api.setId(apiId);
            val apiRequest = new ApiRequest.Builder()
                    .setRequestUri(requestUri)
                    .setMethod(method)
                    .setFieldParser(fieldParser)
                    .build();
            api.setName(requestUri);
            api.setDescription("");
            if (marvinApi != null) {
                api.setName(marvinApi.value());
                api.setDescription(marvinApi.description());
            }
            val apiResponse = new ApiResponse.Builder()
                    .setMethod(method)
                    .setFieldParser(fieldParser)
                    .build();
            api.setRequest(apiRequest);
            api.setResponse(apiResponse);
            allApi.put(api.getId(), api);
            apiMap.put(api.getId(), api);
        }
        apiGroup.setApiMap(apiMap);
        apiSet.add(apiGroup);
        return bean;
    }

    public Set<ApiGroup> getApiGroupList() {
        return this.apiSet;
    }

    public Map<Integer, Api> getAllApi() {
        return this.allApi;
    }

    public List<ApiCount> getApiCountList() {
        List<ApiCount> apiCounts = new ArrayList<>();
        ApiCount totalApiCount = apiCounterMap.get("total_api_count");
        if (totalApiCount != null) {
            apiCounts.add(totalApiCount);
        }
        List<ApiCount> subApiCountList = new ArrayList<>();
        for (Map.Entry<String, ApiCount> entry : apiCounterMap.entrySet()) {
            if (entry.getKey().equals("total_api_count")) {
                continue;
            }
            subApiCountList.add(entry.getValue());
        }
        Collections.sort(subApiCountList);
        apiCounts.addAll(subApiCountList);
        return apiCounts;
    }

    private void countApi(MarvinApi marvinApi) {
        if (null == marvinApi) {
            return;
        }
        increaseApiCount("total_api_count", "总数");
        SupportClientType[] supportClientTypes = marvinApi.supportClientType();
        if (supportClientTypes.length == 0) {
            increaseApiCount("count_api_count", "其他");
            return;
        }
        for (SupportClientType supportClientType : supportClientTypes) {
            increaseApiCount(supportClientType.getCode() + "_api_count", supportClientType.getName());
        }
    }

    private void increaseApiCount(String key, String name) {
        ApiCount apiCount = apiCounterMap.get(key);
        if (apiCount != null) {
            int count = apiCount.getCount() + 1;
            apiCount.setCount(count);
        } else {
            apiCount = new ApiCount();
            apiCount.setKey(key);
            apiCount.setName(name);
            apiCount.setCount(1);
        }
        apiCounterMap.put(key, apiCount);
    }
}
